/*
 * Lexer, Parser, Treewalker for Matpix
 *
 * Oliver Cossairt, Ben London, David Burkat, Robert Hawkins
 * Fall 2007
 */
//////////////////////////////////////////////////////////////////////

class MPLexer extends Lexer;
options {
    testLiterals = false; // By default, don't check tokens against keywords
    k = 2;                // Need to decide when strings literals end
    charVocabulary = '\3'..'\377'; // Accept all eight-bit ASCII characters
}

{
    int error_count = 0;
    public void reportError(String s) {
        super.reportError(s);
        error_count++;
    }
    public void reportError(RecognitionException e) {
        super.reportError(e);
        error_count++;
    }
}


PLUS   : '+' ;
MINUS  : '-' ;
MUL    : ".*";
DIV    : "./";
MOD	   : '%' ;
ASSIGN : '=' ;
SEMI   : ';' ; 
DOTPROD: '*' ;
COMMA  : ',' ;
COLON  : ':' ;
POUND  : '#' ;

LBRACKET : '[' ;
RBRACKET : ']' ;
LSCOPE : '{' ;
RSCOPE : '}' ;
ROWSEP : '|' ;

NOT : '!';
AND : "&&";
OR : "||";
LOGGT : '>';
LOGLT : '<';
LOGGTE : ">=";
LOGLTE : "<=";
LOGEQ : "==";
LOGNEQ : "!=";
LPAREN : "(" ;
RPAREN : ")" ;
INC : "++";
DEC : "--";
TRANS : "'";

// A little unorthodox: most punctuation characters get their own rule,
// but since we're using "(" and ")" in the parser, we need parenthesis
// to match as keywords, Thus, we set testLiterals true for this rule.

protected LETTER : ( 'a'..'z' | 'A'..'Z' ) ;
protected DIG : '0'..'9' ;
protected INT : (DIG)+ ;
protected NUMBER : INT ('.' INT)?
				 | '.' INT
				 ;

protected EXPON : ('e' | 'E') (MINUS)? INT ;
protected FLOAT : NUMBER (EXPON)?
				;

SL_COMMENT : "//" (~'\n')* { $setType(Token.SKIP); }; 
ML_COMMENT : "/*" (options { greedy = false; } : .)* "*/" { $setType(Token.SKIP); }; 

ID
options {
    testLiterals = true;
}
    : LETTER (LETTER | DIG | '_')* ;

SCALAR : (MINUS FLOAT) | FLOAT ;

// Strings are "like this ""double quotes"" doubled to include them"
// Note that testLiterals are false so we don't have to worry about
// strings such as "if"
STRING :  '"'! ( '"' '"'! | ~('"'))*  '"'!; 

WS  :   ( ' '
        | '\t'
        | '\n' { newline(); }
        | '\r'
        ) { $setType(Token.SKIP); }
    ;

//////////////////////////////////////////////////////////////////////
class MPParser extends Parser;
options {
    buildAST = true; // Enable AST building
    k = 2;           // Need to distinguish between ID by itself and ID ASSIGN
}

tokens {
  ARGS;
  ARRAY_SLICE;
  CONST_MATRIX;
  CONST_SCALAR;
  CONST_STRING;
  ELSE;
  EXPR;
  FOR;
  FOR_ITER;
  FUNC_CALL;
  FUNC_DEF;
  IF;
  IF_ELSE;
  PRED;
  PROGRAM;
  MATRIX_ROW;
  MATRIX_COL;
  MATRIX_DECLR;
  NEW_SCOPE;
  RANGE_ALL;
  RANGE_ONE;
  RANGE_TWO;
  RANGE_THREE;
  RANGE_LIST;
  RETURN;
  SIGN_MINUS;
  STATEMENT;
  VARIABLE;
  WHILE;
}


{
    int error_count = 0;
    public void reportError(String s) {
        super.reportError(s);
        error_count++;
    }
    public void reportError(RecognitionException e) {
        super.reportError(e);
        error_count++;
    }
}


program    
	: (stmt)+ EOF!
        { #program = #([PROGRAM, "PROGRAM"], program); }
    ; 

// a matrix consist of one or more rows
constant : SCALAR 
			{#constant = #([CONST_SCALAR, "CONST_SCALAR"], #constant);}			
		 | constmatrix
		 | STRING
			{#constant = #([CONST_STRING, "CONST_STRING"], #constant);}			
		 ;

arraySlice : LBRACKET! rangeList RBRACKET!
		   ;

// max 2D matrix
rangeList : range COMMA! range
		{#rangeList = #([RANGE_LIST, "RANGE_LIST"], rangeList);}
		  ;

range! : (
			r1:expr {#range = #([RANGE_ONE, "RANGE_ONE"], r1);}
			(COLON! r2:expr {#range = #([RANGE_TWO, "RANGE_TWO"], r1, r2);} 
				(COLON!  r3:expr {#range = #([RANGE_THREE, "RANGE_THREE"], r1, r3);})?
			)?
		) 
		| COLON!
			{#range = #([RANGE_ALL, "RANGE_ALL"]);}
	  ;

constmatrix
		: LBRACKET! (row)+ RBRACKET!
			{#constmatrix = #([CONST_MATRIX, "CONST_MATRIX"], constmatrix);}
	   	;
// a row consists of one or more comma separated expressions
row : cols
	{#row = #([MATRIX_ROW, "MATRIX_ROW"], row);}
	(ROWSEP!)?
	;

cols : SCALAR (COMMA! SCALAR)*
		{#cols = #([MATRIX_COL, "MATRIX_COL"], cols);}
		;

matrixdecl : "matrix"! ID (LBRACKET! expr COMMA! expr RBRACKET!)?
		{#matrixdecl = #([MATRIX_DECLR, "MATRIX_DECLR"], matrixdecl);}
	   ;

stmt : expr SEMI!
        { #stmt = #([STATEMENT, "STATEMENT"],stmt); }
	 | functionDefinition SEMI! 
	 | controlflow 
	 | "return"! e:expr SEMI!
	 	{#stmt = #([RETURN, "RETURN"], e);}
	 | SEMI!
     | LSCOPE! (stmt)+ RSCOPE!
    	{#stmt = #([NEW_SCOPE, "NEW_SCOPE"], stmt);}
	 ;

expr :	matrixdecl (ASSIGN^ logicExpr)?
	|	logicExpr (ASSIGN^ logicExpr)?
	 ;

logicExpr : predExpr ((AND^|OR^) logicExpr)?
		  ; 
  
predExpr : ((NOT^)? addExpr) ((LOGGT^|LOGLT^|LOGGTE^|LOGLTE^|LOGEQ^|LOGNEQ^) predExpr)?
		 ;

addExpr : mulExpr ( (PLUS^ | MINUS^) mulExpr )*
		; 

mulExpr : signExpr ( (MUL^ | DIV^ | MOD^ | DOTPROD^) signExpr )*
		;

signExpr : (m:MINUS^ {#m.setType(SIGN_MINUS);})? incExpr ;

incExpr : atom (INC^ | DEC^ | TRANS^)?
		;

atom 
	: name:identifier {#atom = #([VARIABLE, "VARIABLE"], name);}
		(
			slice:arraySlice! {#atom = #([ARRAY_SLICE, "ARRAY_SLICE"], atom, slice);} 
			| argList {#atom = #([FUNC_CALL, "FUNC_CALL"], name);}
		)?
	| constant
    | LPAREN! expr RPAREN!
    ;

identifier : ID ;

functionDefinition :  
    "function"! ID defArgList LSCOPE! newScope RSCOPE!
    	{#functionDefinition = #([FUNC_DEF, "FUNC_DEF"], #functionDefinition);}
    ; 

newScope : (stmt)*
    	{#newScope = #([NEW_SCOPE, "NEW_SCOPE"], #newScope);}
    ;

defArgList : LPAREN! (ID (COMMA! ID)* )? RPAREN!
    	{#defArgList = #([ARGS, "ARGS"], #defArgList);}
		   ;
		   
argList : LPAREN! (expr (COMMA! expr)* )? RPAREN! 
    	{#argList = #([ARGS, "ARGS"], #argList);}
        ; 

controlflow : (ifstmt | whilestmt | forstmt)
			;

predicate: logicExpr
    	{#predicate = #([PRED, "PRED"], #predicate);}
	;

ifstmt! :
	"if"! LPAREN! pred:predicate RPAREN! 
	LSCOPE! body:newScope RSCOPE!  
		{#ifstmt = #([IF, "IF"], pred, body);}
	("else"! LSCOPE! elseBody:newScope RSCOPE! 
		{#ifstmt = #([IF_ELSE, "IF_ELSE"], pred, elseBody);} )? 
	;  

whilestmt :
	"while"! LPAREN! predicate RPAREN!  
	LSCOPE! newScope  RSCOPE!
    	{#whilestmt = #([WHILE, "WHILE"], #whilestmt);}
	;  
       
forstmt! :
	"for"^ LPAREN! iter:ID ASSIGN^ iterRange:range RPAREN!
	LSCOPE! body:newScope RSCOPE!
		{#forstmt = #([FOR, "FOR"], 
			#([FOR_ITER, "FOR_ITER"],
				#(ASSIGN, iter, iterRange)), 
			body);}
	;

class MPWalker extends TreeParser;
{
	MPSymbolTable top = new MPSymbolTable(null);
	int constMatNum = 0;
}
program returns [String r] {r = ""; MPExpression e;String a;} : 
	#(PROGRAM
		(
	 		a=newScope {r += a + "\n";}
			| a=stmt {r += top.getTabs()+a + "\n";}
			| FUNC_DEF
			| ~(FUNC_DEF | STATEMENT | NEW_SCOPE | FOR | IF | IF_ELSE | RETURN | WHILE)
			{
				MPException.error("Syntax error.");
			}
		)+
	)
	;

stmt returns [String r]
	{
		r = "";
		MPExpression e;
		String ctrl, a;
	}
	: #(STATEMENT (e=expr 
	{
		if(!(e.pre.equals("")))
			r += e.pre + ";\n"+top.getTabs();
		r += e.s + ";";
	}
	| e=matDecl {r = e.s + ";";}
	))
	| ctrl=controlFlow { r = ctrl; }
	| e=return_stmt { r = "return "+ e.s + ";"; }
	;

return_stmt returns [MPExpression r]
	{
		r = new MPExpression();
	}
	: #(RETURN r=expr)
	;
	

controlFlow returns [String r]
	{
		r = "";
		String a;
	}
	: r=if_stmt
	;

if_stmt returns [String r] 
	{ 
		r = ""; 
		MPExpression var1;
		String var2, var3;
		String[] rlist;
	}
	: #(IF { r = "if ("; }
		#(PRED var1=logicExpr {r += "("+var1.s+").getFloatValue()";}) 
		{ r += ") "; }
		var2=newScope {r += var2;}
	   )
	| #(IF_ELSE { r = "if ("; }
		#(PRED var1=logicExpr {r += "("+var1.s+").getFloatValue()";}) 
		{ r += ") "; }
		var2=newScope {r += var2;} 
		{ r += top.getTabs()+"else "; }
		var2=newScope {r += var2;}
	   )
	| #(WHILE { r = "while ("; }
		#(PRED var1=logicExpr {r += "("+var1.s+").getFloatValue()";}) 
		{ r += ") "; }
		var2=newScope {r += var2;}
	   )
	| #(FOR { r = "for ("; }
		#(FOR_ITER 
			#(ASSIGN iter:ID rlist=forRange) 
				{
					top.putV(iter.getText());
					String mpname = MPExpression.MPName(iter.getText());
					if ( rlist.length == 2 ) {
						String a,c;
						try
						{
							Float.parseFloat(rlist[0]);							
							a = rlist[0];
						}
						catch(NumberFormatException e)
						{
							a = rlist[0] + ".getFloatValue()";
						}
						try
						{
							Float.parseFloat(rlist[1]);							
							c = rlist[1];
						}
						catch(NumberFormatException e)
						{
							c = rlist[1] + ".getFloatValue()";
						}
						r += "Iter "+mpname+"="+a+";"+mpname+"<="+c+";++"+mpname;
					}
					else if ( rlist.length == 3 ) {
						String a,b,c;
						try
						{
							Float.parseFloat(rlist[0]);							
							a = rlist[0];
						}
						catch(NumberFormatException e)
						{
							a = rlist[0] + ".getFloatValue()";
						}
						try
						{
							Float.parseFloat(rlist[1]);							
							b = rlist[1];
						}
						catch(NumberFormatException e)
						{
							b = rlist[1] + ".getFloatValue()";
						}
						try
						{
							Float.parseFloat(rlist[2]);							
							c = rlist[2];
						}
						catch(NumberFormatException e)
						{
							c = rlist[2] + ".getFloatValue()";
						}
						r += "Iter "+mpname+"="+a+";"+mpname+">="+c+";"+mpname+"+="+b;
					}
				}
		 )
		{ r += ") "; }
		var2=newScope {r += var2;}
	   )
	;

forRange returns [String[] r]
	{
		r = new String[3];
		String[] r2;
		String[] r3;
	}
	: r2=forRangeTwo { r = r2; }
	| r3=forRangeThree { r = r3; }
	;
	
forRangeTwo returns [String[] r] { r = new String[2]; MPExpression a,b;}
	: #(RANGE_TWO a=expr b=expr)
		{
			try
			{
				r[0] = a.s;
				r[1] = b.s;
			}
			catch(NumberFormatException e)
			{
				MPException.error("cannot parse float ");	
			}
		}
	;
forRangeThree returns [String[] r] { r = new String[3]; MPExpression a,b,c;}
	: #(RANGE_THREE a=expr b=expr c=expr) 
		{
			r[0] = a.s;
			r[1] = b.s;
			r[2] = c.s;
		}
	;

logicExpr returns [MPExpression r] 
	{
		r = new MPExpression();
		MPExpression expr1, expr2;
	}
	: #(LOGEQ expr1=expr expr2=expr) { r.s = expr1.s + "==" + expr2.s; }
	| #(LOGNEQ expr1=expr expr2=expr) { r.s = expr1.s + "!=" + expr2.s; }
	| #(LOGGT expr1=expr expr2=expr) { r.s = expr1.s + ">" + expr2.s; }
	| #(LOGLT expr1=expr expr2=expr) { r.s = expr1.s + "<" + expr2.s; }
	| #(LOGGTE expr1=expr expr2=expr) { r.s = expr1.s + ">=" + expr2.s; }
	| #(LOGLTE expr1=expr expr2=expr) { r.s = expr1.s + "<=" + expr2.s; }
	| #(AND expr1=expr expr2=expr) { r.s = expr1.s + "&&" + expr2.s; }
	| #(OR expr1=expr expr2=expr) { r.s = expr1.s + "||" + expr2.s; }
	;

// NOTE: LVAR PRINTS UNCLOSED PARENTHESIS, ASSIGN CLOSES PARENTHESIS AFTER
// INSERTING ASSIGNEE AS ARGUMENT		
lvar returns [MPExpression r]
	{
		r = new MPExpression();
		String slice, var;
		MPExpression a;
	}
	: #(VARIABLE id:ID )
	{ 
		String name = id.getText();
		r.name = name;
		if (!top.containsV(name)) {
			top.putV(name);
			r.s += "Matrix "+r.MPName()+"(0);\n"+top.getTabs();
		}
		r.s += r.MPName();
		r.s += ".assign(";
	}
	| a=matDecl
		{
			top.putV(a.name);
			r.s = a.s+";\n"+top.getTabs()+a.MPName()+".assign(";
		}
	| #(ARRAY_SLICE #(VARIABLE id1:ID) slice=lRangeList)
	{ 
		r.name = id1.getText();
		r.s = r.MPName();
		r.s += slice;
		if (!top.containsV(r.name)) {
	 		String message = "Cannot index matrix " + r.name + " before it is initialized.";
			MPException.error(message);
		}
	}
	|
		node:~(VARIABLE | MATRIX_DECLR | ARRAY_SLICE)
		{
			MPException.error(node.getText()+" cannot appear to the left of an assignment.");
		}
	;
	
lRangeList returns [String r] 
	{ 
		r = "";
		String var1, var2;
	}
	: #(RANGE_LIST
		var1=lrange { r = ".sliceAssign(RangeList("+var1+"),"; }
		(var2=lrange { r += "RangeList("+var2+"),"; })?
	   )
	;

rRangeList returns [String r] 
	{ 
		r = "";
		String var1, var2;
	}
	: #(RANGE_LIST
		var1=lrange { r = ".slice(RangeList("+var1+")"; }
		(var2=lrange { r += ",RangeList("+var2+")"; })?
	   )
		{ r += ")"; }
	;

lrange returns [String r]
	{
		r = "";
		String rAll,r1,r2,r3;
	}
	: RANGE_ALL
	| r1=rangeOne { r = r1; }
	| r2=rangeTwo { r = r2; }
	| r3=rangeThree { r = r3; }
	;
	
rangeOne returns [String r] {r=""; MPExpression a;}
	: #(RANGE_ONE a=expr) 
	{
		String as=a.s;
		try
		{
			Float.parseFloat(as);							
		}
		catch(NumberFormatException e)
		{
			as += ".getFloatValue()";
		}
		r = as;
	}
	;
rangeTwo returns [String r] {r=""; MPExpression a,b;}
	: #(RANGE_TWO a=expr b=expr) 
	{
		String as=a.s;
		String bs=b.s;
		try
		{
			Float.parseFloat(as);							
		}
		catch(NumberFormatException e)
		{
			as += ".getFloatValue()";
		}
		try
		{
			Float.parseFloat(bs);							
		}
		catch(NumberFormatException e)
		{
			bs += ".getFloatValue()";
		}
		r = as + "," + bs;
	}
	;
rangeThree returns [String r] {r=""; MPExpression a,b,c;}
	: #(RANGE_THREE a=expr b=expr c=expr) 
	{
		String as=a.s;
		String bs=b.s;
		String cs=c.s;
		try
		{
			Float.parseFloat(as);							
		}
		catch(NumberFormatException e)
		{
			as += ".getFloatValue()";
		}
		try
		{
			Float.parseFloat(bs);							
		}
		catch(NumberFormatException e)
		{
			bs += ".getFloatValue()";
		}
		try
		{
			Float.parseFloat(cs);							
		}
		catch(NumberFormatException e)
		{
			cs += ".getFloatValue()";
		}
		r= as + "," + bs + "," + cs;
	}
	;

// NOTE: LVAR PRINTS UNCLOSED PARENTHESIS, ASSIGN CLOSES PARENTHESIS AFTER
// INSERTING ASSIGNEE AS ARGUMENT		
assignment returns [MPExpression r]
	{
		r = new MPExpression();
		MPExpression a, b;
	}
	:
	#(ASSIGN  (a=lvar b=expr)
		{
		r.s += a.s+b.s+")";
		r.pre = b.pre;
		}
	)
	;
binop returns [MPExpression r]
	{
		r = new MPExpression();
		MPExpression a,b;
	}
	: #(DOTPROD  a=expr b=expr)
	{
		float aFloat, bFloat;
		try { // is left float?
			aFloat = Float.parseFloat(a.s);											
			try { // is right float?
				bFloat = Float.parseFloat(b.s);
				// both floats
				// normal scalar arithmetic
				r.s = aFloat + "*" + bFloat;
			}
			catch(NumberFormatException e1) {
				// left is float and right is variable
				// swap left and right
		  		r.s = b.s+".mul("+a.s+")";
		  	}
		}
		catch(NumberFormatException e1) {
			try { // is right float?
				bFloat = Float.parseFloat(b.s);
				// left is variable and right is float
		  		r.s = a.s+".mul("+b.s+")";
			}
			catch(NumberFormatException e2) {
				// both are variables
				// execute dotprod
				r.s = a.s+".dot("+b.s+")";
		  	}
		}		  	
	}
	| 
	(
		  #(MUL  a=expr b=expr) {
			float aFloat, bFloat;
			try { // is left var float?
				aFloat = Float.parseFloat(a.s);											
				try { // is right var float?
					bFloat = Float.parseFloat(b.s);
					// both floats
					// normal scalar arithmetic
					r.s = aFloat + "*" + bFloat;
				}
				catch(NumberFormatException e) {
					// left is float and right is variable
					// swap left and right
			  		r.s = b.s+".mul("+a.s+")";
			  	}
			}
			catch(NumberFormatException e) {
				// left is variable
				r.s = a.s+".mul("+b.s+")";
			}		  	
		  }
		| #(DIV  a=expr b=expr) {
			float aFloat, bFloat;
			try { // is left var float?
				aFloat = Float.parseFloat(a.s);											
				try { // is right var float?
					bFloat = Float.parseFloat(b.s);
					// both floats
					// normal scalar arithmetic
					r.s = aFloat + "/" + bFloat;
				}
				catch(NumberFormatException e) {
					// left is float and right is variable
					// convert left to matrix of same size
					// and execute op
			  		r.s = "Matrix("+a.s+","+b.s+".w_,"+b.s+".h_).div("+b.s+")";
			  	}
			}
			catch(NumberFormatException e) {
				// left is variable
				r.s = a.s+".div("+b.s+")";
			}		  	
		  }
		| #(MOD  a=expr b=expr) {
			float aFloat, bFloat;
			try { // is left var float?
				aFloat = Float.parseFloat(a.s);											
				try { // is right var float?
					bFloat = Float.parseFloat(b.s);
					// both floats
					// normal scalar arithmetic
					r.s = aFloat + "%" + bFloat;
				}
				catch(NumberFormatException e) {
					// left is float and right is variable
					// convert left to matrix of same size
					// and execute op
			  		r.s = "Matrix("+a.s+","+b.s+".w_,"+b.s+".h_).mod("+b.s+")";
			  	}
			}
			catch(NumberFormatException e) {
				// left is variable
				r.s = a.s+".mod("+b.s+")";
			}		  	
		  }
		| #(PLUS  a=expr b=expr) {
			float aFloat, bFloat;
			try { // is left var float?
				aFloat = Float.parseFloat(a.s);											
				try { // is right var float?
					bFloat = Float.parseFloat(b.s);
					// both floats
					// normal scalar arithmetic
					r.s = aFloat + "+" + bFloat;
				}
				catch(NumberFormatException e) {
					// left is float and right is variable
					// swap left and right
			  		r.s = b.s+".add("+a.s+")";
			  	}
			}
			catch(NumberFormatException e) {
				// left is variable
				r.s = a.s+".add("+b.s+")";
			}		  	
		  }
		| #(MINUS  a=expr b=expr) {
			float aFloat, bFloat;
			try { // is left var float?
				aFloat = Float.parseFloat(a.s);											
				try { // is right var float?
					bFloat = Float.parseFloat(b.s);
					// both floats
					// normal scalar arithmetic
					r.s = aFloat + "-" + bFloat;
				}
				catch(NumberFormatException e) {
					// left is float and right is variable
					// convert left to matrix of same size
					// and execute op
			  		r.s = "Matrix("+a.s+","+b.s+".w_,"+b.s+".h_).sub("+b.s+")";
			  	}
			}
			catch(NumberFormatException e) {
				// left is variable
				r.s = a.s+".sub("+b.s+")";
			}		  	
		  }
	)
	;

variable returns [String r]
	{
		r = "";
	}
	:
		#(VARIABLE id:ID) 
		{
			String var=id.getText();
			if (!top.containsV(var)) {
				MPException.error("Variable <"+var+"> does not exist.");
			}
			r = MPExpression.MPName(var); 
		}
	;


expr returns [MPExpression r]
	{
		r = new MPExpression();
		MPExpression a,b, constmat;
		String rlist, var;
	}
	: a=assignment { r = a; }
	| a=logicExpr { r = a; }
	| a=binop {r = a;}
	| #(SIGN_MINUS a=expr)
	{
		r.s = "Matrix("+a.s+").negate()";
	}
	| #(TRANS  a=expr)
	{
		r.s = a.s + ".transpose()";
	}
 	| #(ARRAY_SLICE var=variable rlist=rRangeList)
		{ 
			r.s = var + rlist; 
		}
	| #(INC var=variable)
		{
			r.s = var + "++";
		}
	| #(DEC var=variable)
		{
			r.s = var + "--";
		}
	| #(NOT var=variable)
		{
			r.s = var + ".notOp()";
		}
	| var=variable
		{
			r.s = var;
		}
	| #(CONST_SCALAR s:SCALAR)
		{
			r.s = s.getText();
		}
	| #(CONST_STRING str:STRING)
		{
			String myStr = str.getText().replaceAll("\"", "\\\\\"");
			r.s = "\"" + myStr + "\"";
		}
	| #(CONST_MATRIX constmat=constmatrix) { r.s =  constmat.s; r.pre = constmat.pre;}
	| #(FUNC_CALL funcName:ID
	{
		java.util.ArrayList<String> args = new java.util.ArrayList<String>();
	}
		#(ARGS	(a=expr	{
					args.add(a.s);
					}
			)*
		)
		)
		{
			String fname = funcName.getText();
			if ( fname.equals("print")) {
				if (args.size() == 0) {
					r.s += "cout << endl";
				}
				else {
					r.s += "cout << ";
				}
				for (int i = 0; i < args.size(); i++) {
					if (i==args.size()-1) {
						if (args.get(i).substring(0,1).equals("\""))
							r.s += args.get(i) + " << endl";
						else
							r.s += args.get(i);
					}
					else
						r.s += args.get(i) + " << ";
				}
			}
			else {
				if (top.containsF(fname, args.size())) {
					r.s = fname + "(";
					for (int i = 0; i < args.size(); i++) {
						r.s += args.get(i) + ",";
					}
					if (args.size() > 0) {
						r.s = r.s.substring(0, r.s.length()-1);
					}
					r.s += ")";
				}
				else {
					MPException.error("Function <"+fname+"> with "+args.size()+" arguments has not been defined.");
				}
			}
		}
	;

newScope returns [String r]
	{
		MPSymbolTable savedScope = top;
		top = new MPSymbolTable(savedScope);
		r=" {\n";
		String a="";
	}
	:
		#(NEW_SCOPE
			(a=stmt {r += top.getTabs()+a+"\n";})*
		)
		{
			top = savedScope;
			r += top.getTabs()+"}\n";
		}
		;


//Matches multiple function definitions
funcDef returns [String r] 
	{
		r="";
		String args = "";
		String arg_names = "";
		String a, b;
		String fname;
		MPSymbolTable savedScope = null;
	}:
	#(PROGRAM
		(#(FUNC_DEF name:ID
			{
				fname = name.getText();
				r += "Matrix "+fname+" (";
				arg_names = "";
				args = "";
			}
			#(ARGS
				(id:ID
					{
						args+="Matrix " + MPExpression.MPName(id.getText())+",";
						arg_names += id.getText() + " ";
					}
			    )*
			 )
		    {
				if (args.length() > 0)
		    		args=args.substring(0, args.length()-1);
				r += args+") ";
		    }
		    #(fb:NEW_SCOPE
		    {
		    	String[] arglist;
				if (arg_names.equals("")) {
					arglist = new String[0];
				}
				else {
					arglist = arg_names.split(" ");
				}
		    	if (!top.containsF(fname, arglist.length)) {
					top.putF(name.getText(), arglist.length);
					savedScope = top;
					top = new MPSymbolTable(savedScope, 0);
					for (int i = 0; i < arglist.length; i++) {
						top.putV(arglist[i]);
					}
		    	}
		    	else {
		    		MPException.error("A function named <"+fname+"> with "+arglist.length+" arguments already exists.");
		    	}
				b = newScope(fb);
				r += b+"\n";
				top = savedScope;
		    }
			)
		)
		{arg_names = "";}
		| ~(FUNC_DEF)
		)*
	)
	;
matDecl returns [MPExpression r]
	{
		r=new MPExpression();
		MPExpression xdim, ydim;
	}
	:
	#(MATRIX_DECLR name:ID
		(xdim=expr ydim=expr
			{
				String token = name.getText();
				top.putV(token);
				r.name = token;
				r.s = "Matrix " + MPExpression.MPName(token) + "(" + xdim.s + ", " + ydim.s + ")";
			}
		|
		{
			String token = name.getText();
			top.putV(token);
			r.name = token;
			r.s = "Matrix " + MPExpression.MPName(token) + "(0)";
		}
		)
	)

  	;
  	
constmatrix returns [MPExpression r]
	{
		constMatNum++;
		int w =0;
		int h = -1;
		r = new MPExpression();
		String arrayName = "array_" + Integer.toString(constMatNum);
		String vecName = "vec_" + Integer.toString(constMatNum);
		r.pre="vector<float> " + vecName + ";\n";
		r.pre+=top.getTabs()+"float " + arrayName + "[] = {";
		java.util.ArrayList<String> a;
	}
		: 
		(a=row 
		{
			if(!(h == a.size() || h == -1))
				MPException.error("size of columns must be the same for constant matrix declaration");
			w = w+1;
			h = a.size();
			for(int i=0; i < h; i++)
			{
					String val = a.get(i);
					r.pre+=val + ",";
			}
		}
		)+
		{
			r.pre = r.pre.substring(0, r.pre.length()-1);
			r.pre+="};\n"; 
			r.pre+=top.getTabs()+"copy(" + arrayName + ", " + arrayName + "+" + Integer.toString(w*h) + ", back_inserter(" + vecName + "))";
			r.s = vecName + ", " + Integer.toString(w) + ", " + Integer.toString(h);
		}
	   	;

// a row consists of one or more comma separated expressions
row returns [java.util.ArrayList<String> r]
	{
		r = new java.util.ArrayList<String>();
		java.util.ArrayList<String> a;
	}
	: #(MATRIX_ROW a=cols) 
	{
		r=a;
	} 
	;

cols returns [java.util.ArrayList<String> r]
	{
		r = new java.util.ArrayList<String>();
	}
	: #(MATRIX_COL
		(
			a:SCALAR
				{
					r.add(a.getText());
				} 		
		)+
	  )
	;
  	